package lang4j.generator;

import lang4j.parser.generated.*;
import org.apache.commons.lang.StringUtils;

import java.util.Iterator;
import java.util.List;

/**
 * User: felix
 * Date: 09.11.2005
 * Time: 21:40:18
 */
public class RenderMethodsGenerator extends Object {
    private Lang4jGrammar grammar;
    private Transformer transformer;
    StringBuffer out;

    public RenderMethodsGenerator(final Lang4jGrammar grammar, final Transformer transformer) {
        this.grammar = grammar;
        this.transformer = transformer;
        out = new StringBuffer();
        renderMethods();
    }

    private void renderMethods() {
        for (Iterator iterator = grammar.getProductions().iterator(); iterator.hasNext();) {
            Production production = (Production) iterator.next();
            production.accept(new ProductionRenderer());
        }
    }


    public String getText() {
        return out.toString();
    }

    private class ProductionRenderer implements ProductionVisitor, TypeProductionVisitor, ListProductionVisitor {

        public void visitTypeProduction(TypeProduction typeProduction) {
            typeProduction.accept((TypeProductionVisitor) this);
        }

        public void visitEnumProduction(EnumProduction enumProduction) {
            String name = enumProduction.getName();
            String capName = StringUtils.capitalize(name);
            renderStaticMethod(capName, name, capName);
            renderRenderMethod(capName, name);
            String output = name + ".getId()";
            renderOutputStatement(output);
            renderEndOfMethod();
        }

        private void renderEndOfMethod() {
            out.append("    }\n\n");
        }

        private void renderOutputStatement(final String output) {
            out.append("        out.print(" + output + ");\n");
        }

        private void renderLiteralOutputStatement(final String output) {
            out.append("        out.print(\"" + output + "\");\n");
        }

        private void renderRenderMethod(final String capName, final String name) {
            out.append("    public void render" + capName + "(" + capName + " " + name + "){\n");
        }


        public void visitListProduction(ListProduction listProduction) {
            renderStaticMethod(StringUtils.capitalize(listProduction.getName()), listProduction.getName(), Transformer.LIST_INTERFACE);
            listProduction.accept((ListProductionVisitor) this);
        }

        public void visitInterfaceProduction(InterfaceProduction interfaceProduction) {
            String name = interfaceProduction.getName();
            String capName = StringUtils.capitalize(name);
            renderStaticMethod(capName, name, capName);
            renderRenderMethod(capName, name);
            out.append("    " + name + ".accept(new " + capName + "Visitor(){\n"
            );
            for (Iterator iterator = interfaceProduction.getSubTypes().iterator(); iterator.hasNext();) {
                TypeProduction subType = (TypeProduction) iterator.next();
                String vName = subType.getName();
                String cvName = StringUtils.capitalize(vName);
                out.append("        public void visit" + cvName + ("(" + cvName + " " + vName + "){\n"));
                out.append("            render" + cvName + "(" + vName + ");\n");
                out.append("        }\n");
            }
            out.append("    });\n");
            renderEndOfMethod();

        }

        private void renderStaticMethod(final String capName, final String name, final String className) {
            out.append("   public static String " + name + "ToString(" + className + " " + name + "){\n");
            out.append("        StringWriter writer=new StringWriter();\n");
            String rendererClass = grammar.getName() + "Renderer";
            out.append("        " + rendererClass + " renderer=new " + grammar.getName() + "Renderer(writer);\n");
            out.append("        renderer.render" + capName + "(" + name + ");\n");
            out.append("        return writer.getBuffer().toString();\n");
            out.append("    }\n");
        }

        public void visitConstructorProduction(ConstructorProduction constructorProduction) {
            String name = constructorProduction.getName();
            String capName = StringUtils.capitalize(name);
            renderStaticMethod(capName, name, capName);
            renderRenderMethod(capName, name);
            visitProductionFactors("", constructorProduction, constructorProduction.getFactors());
            renderEndOfMethod();
        }

        private void visitProductionFactors(final String indent, final ConstructorProduction parentProduction, final List factors) {
            ProductionFactorRenderer visitor = new ProductionFactorRenderer(indent, parentProduction, factors);
            for (Iterator iterator = factors.iterator(); iterator.hasNext();) {
                ProductionFactor productionFactor = (ProductionFactor) iterator.next();
                productionFactor.accept(visitor);
            }
        }

        public void visitListProductionStar(ListProductionStar listProductionStar) {
            String name = listProductionStar.getName();
            String capName = StringUtils.capitalize(name);
            final String elementName = listProductionStar.getType().getProduction().getName();
            final String elementClass = StringUtils.capitalize(elementName);
            out.append("    public void render" + capName + "(" + Transformer.LIST_INTERFACE + " " + name + "){\n");
            out.append("        for(Iterator iter=" + name + ".iterator();iter.hasNext();){\n");
            out.append("            " + elementClass + " " + elementName + " = (" + elementClass + ") iter.next();\n");

            renderListElementRendering(listProductionStar, elementClass, elementName);


            out.append("    ");
            renderOutputStatement("\"\\n\"");
            out.append("        }\n");

            renderEndOfMethod();
        }

        private void renderListElementRendering(final ListProduction listProductionStar, final String elementClass, final String elementName) {
            listProductionStar.getType().accept(new ListElementTypeVisitor() {
                public void visitProductionReference(ProductionReference productionReference) {
                    out.append("            render" + elementClass + "(" + elementName + ");\n");
                }

                public void visitReferenceElement(ReferenceElement referenceElement) {
                    String cAttribName = StringUtils.capitalize(referenceElement.getProduction().getKeyField());
                    renderOutputStatement(elementName + ".get" + cAttribName + "()");
                }
            });
        }

        public void visitListProductionPow(ListProductionPow listProductionPow) {
            String name = listProductionPow.getName();
            String capName = StringUtils.capitalize(name);
            String elementName = listProductionPow.getType().getProduction().getName();
            String elementClass = StringUtils.capitalize(elementName);
            out.append("    public void render" + capName + "(" + Transformer.LIST_INTERFACE + " " + name + "){\n");
            out.append("        for(Iterator iter=" + name + ".iterator();iter.hasNext();){\n");
            out.append("            " + elementClass + " " + elementName + " = (" + elementClass + ") iter.next();\n");
            renderListElementRendering(listProductionPow, elementClass, elementName);
            out.append("    if (iter.hasNext()) ");
            listProductionPow.getLit().accept(new TerminalVisitor() {
                public void visitCharToken(CharToken charToken) {
                    renderLiteralOutputStatement(Character.toString(charToken.getText()));
                }

                public void visitKeyword(Keyword keyword) {
                    renderLiteralOutputStatement(keyword.getText() + " ");
                }
            });
            out.append("        }\n");
            renderEndOfMethod();

        }

        private class ProductionFactorRenderer implements ProductionFactorVisitor, AttributeVisitor {
            private final String indent;
            private final ConstructorProduction parentProduction;
            private final List factors;

            public ProductionFactorRenderer(final String indent, final ConstructorProduction parentProduction, final List factors) {

                this.indent = indent;
                this.parentProduction = parentProduction;
                this.factors = factors;
            }

            public void visitTerminal(Terminal terminal) {
                terminal.accept(new TerminalVisitor() {
                    public void visitCharToken(CharToken charToken) {
                        out.append(indent);
                        renderLiteralOutputStatement(Character.toString(charToken.getText()));
                    }

                    public void visitKeyword(Keyword keyword) {
                        renderLiteralOutputStatement(keyword.getText() + " ");
                        out.append(indent);
                    }
                });
            }

            public void visitAttribute(Attribute attribute) {
                attribute.accept((AttributeVisitor) this);
                renderLiteralOutputStatement(" ");
            }

            public void visitOptionalAttributeList(OptionalAttributeList optionalAttributeList) {
                Attribute indicator;
                indicator = determineIndicatorAttribute(optionalAttributeList);

                System.out.println("Indicator: " + indicator);
                if (indicator == null) {
                    return;
                }
                String cAttr = StringUtils.capitalize(indicator.getName());
                if (indicator.getName() == null) {
                    if (indicator instanceof DeepAttribute) {
                        cAttr = StringUtils.capitalize(((DeepAttribute) indicator).getProduction().getName());
                    }
                    else {
                        throw new RuntimeException("Internal Error");
                    }
                }

                out.append(indent + "        if (" + parentProduction.getName() + ".get" + cAttr + "()!=null){\n");
                visitProductionFactors(indent + "    ", parentProduction, optionalAttributeList.getFactors());
                out.append(indent + "        }\n");
            }

            private Attribute determineIndicatorAttribute(final OptionalAttributeList optionalAttributeList) {
                for (Iterator iterator = optionalAttributeList.getFactors().iterator(); iterator.hasNext();) {
                    ProductionFactor productionFactor = (ProductionFactor) iterator.next();
                    if (productionFactor instanceof Attribute) {
                        return (Attribute) productionFactor;
                    }
                }

                for (Iterator iterator = optionalAttributeList.getFactors().iterator(); iterator.hasNext();) {
                    ProductionFactor productionFactor = (ProductionFactor) iterator.next();
                    if (productionFactor instanceof OptionalAttributeList) {
                        return determineIndicatorAttribute((OptionalAttributeList) productionFactor);
                    }
                }
                return null;
            }


            public void visitDeepAttribute(final DeepAttribute deepAttribute) {
                String name = deepAttribute.getProduction().getName();

                String capAttribName = deepAttribute.hasName() ? StringUtils.capitalize(deepAttribute.getName()) :
                        StringUtils.capitalize(deepAttribute.getProduction().getName());
                String cName = StringUtils.capitalize(name);
                out.append(indent + "        render" + cName + "(" + parentProduction.getName() + ".get" + capAttribName + "());\n");
            }

            public void visitFlatAttribute(FlatAttribute flatAttribute) {
                final String cName = StringUtils.capitalize(flatAttribute.getName());
                flatAttribute.accept(new FlatAttributeVisitor() {
                    public void visitIntLiteralAttribute(IntLiteralAttribute intLiteralAttribute) {
                        renderOutputStatement("\"'\"+Integer.toString(" + parentProduction.getName() + ".get" + cName + "())+\"'\"");
                    }

                    public void visitDoubleLiteralAttribute(DoubleLiteralAttribute doubleLiteralAttribute) {
                        renderOutputStatement("\"'\"+Double.toString(" + parentProduction.getName() + ".get" + cName + "())+\"'\"");

                    }

                    public void visitCharLiteralAttribute(CharLiteralAttribute charLiteralAttribute) {
                        renderOutputStatement("\"'\"+StringEscapeUtils.escapeJava(Character.toString(" + parentProduction.getName() + ".get" + cName + "()))+\"'\"");
                    }

                    public void visitBooleanLiteralAttribute(BooleanLiteralAttribute booleanLiteralAttribute) {
                        renderOutputStatement("\"'\"+Boolean.toString(" + parentProduction.getName() + ".get" + cName + "())+\"'\"");
                    }

                    public void visitStringLiteralAttribute(StringLiteralAttribute stringLiteralAttribute) {
                        renderOutputStatement("\"\\\"\"+StringEscapeUtils.escapeJava(" + parentProduction.getName() + ".get" + cName + "())+\"\\\"\"");
                    }

                    public void visitIdentifierAttribute(IdentifierAttribute identifierAttribute) {
                        renderOutputStatement(parentProduction.getName() + ".get" + cName + "()");
                    }

                    public void visitReferenceAttribute(ReferenceAttribute referenceAttribute) {
                        String cKeyAttr = StringUtils.capitalize(referenceAttribute.getProduction().getKeyField());
                        renderOutputStatement(parentProduction.getName() + ".get" + cName + "().get" + cKeyAttr + "()");
                    }

                    public void visitMultiLineStringAttribute(MultiLineStringAttribute multiLineStringAttribute) {
                        renderOutputStatement("\"\\\"\"+StringEscapeUtils.escapeJava(" + parentProduction.getName() + ".get" + cName + "())+\"\\\"\"");
                    }
                });

            }
        }
    }
}
